home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 April / PCWorld_2008-04_cd.bin / v cisle / ozo / zotero-1.0.3.xpi / chrome / zotero.jar / content / zotero / overlay.js < prev    next >
Encoding:
JavaScript  |  2008-01-14  |  63.2 KB  |  2,131 lines

  1. /*
  2.     ***** BEGIN LICENSE BLOCK *****
  3.     
  4.     Copyright (c) 2006  Center for History and New Media
  5.                         George Mason University, Fairfax, Virginia, USA
  6.                         http://chnm.gmu.edu
  7.     
  8.     Licensed under the Educational Community License, Version 1.0 (the "License");
  9.     you may not use this file except in compliance with the License.
  10.     You may obtain a copy of the License at
  11.     
  12.     http://www.opensource.org/licenses/ecl1.php
  13.     
  14.     Unless required by applicable law or agreed to in writing, software
  15.     distributed under the License is distributed on an "AS IS" BASIS,
  16.     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.     See the License for the specific language governing permissions and
  18.     limitations under the License.
  19.     
  20.     ***** END LICENSE BLOCK *****
  21. */
  22.  
  23. /*
  24.  * This object contains the various functions for the interface
  25.  */
  26. var ZoteroPane = new function()
  27. {
  28.     this.collectionsView = false;
  29.     this.itemsView = false;
  30.     
  31.     //Privileged methods
  32.     this.onLoad = onLoad;
  33.     this.onUnload = onUnload;
  34.     this.toggleDisplay = toggleDisplay;
  35.     this.isShowing = isShowing;
  36.     this.fullScreen = fullScreen;
  37.     this.isFullScreen = isFullScreen;
  38.     this.handleKeyDown = handleKeyDown;
  39.     this.handleKeyUp = handleKeyUp;
  40.     this.setHighlightedRowsCallback = setHighlightedRowsCallback;
  41.     this.handleKeyPress = handleKeyPress;
  42.     this.newItem = newItem;
  43.     this.newCollection = newCollection;
  44.     this.newSearch = newSearch;
  45.     this.openAdvancedSearchWindow = openAdvancedSearchWindow;
  46.     this.toggleTagSelector = toggleTagSelector;
  47.     this.updateTagSelectorSize = updateTagSelectorSize;
  48.     this.getTagSelection = getTagSelection;
  49.     this.clearTagSelection = clearTagSelection;
  50.     this.updateTagFilter = updateTagFilter;
  51.     this.onCollectionSelected = onCollectionSelected;
  52.     this.itemSelected = itemSelected;
  53.     this.updateItemIndexedState = updateItemIndexedState;
  54.     this.reindexItem = reindexItem;
  55.     this.duplicateSelectedItem = duplicateSelectedItem;
  56.     this.deleteSelectedItem = deleteSelectedItem;
  57.     this.deleteSelectedCollection = deleteSelectedCollection;
  58.     this.editSelectedCollection = editSelectedCollection;
  59.     this.copySelectedItemsToClipboard = copySelectedItemsToClipboard;
  60.     this.clearQuicksearch = clearQuicksearch;
  61.     this.handleSearchKeypress = handleSearchKeypress;
  62.     this.handleSearchInput = handleSearchInput;
  63.     this.search = search;
  64.     this.selectItem = selectItem;
  65.     this.getSelectedCollection = getSelectedCollection;
  66.     this.getSelectedSavedSearch = getSelectedSavedSearch;
  67.     this.getSelectedItems = getSelectedItems;
  68.     this.getSortedItems = getSortedItems;
  69.     this.getSortField = getSortField;
  70.     this.getSortDirection = getSortDirection;
  71.     this.buildCollectionContextMenu = buildCollectionContextMenu;
  72.     this.buildItemContextMenu = buildItemContextMenu;
  73.     this.onDoubleClick = onDoubleClick;
  74.     this.loadURI = loadURI;
  75.     this.setItemsPaneMessage = setItemsPaneMessage;
  76.     this.clearItemsPaneMessage = clearItemsPaneMessage;
  77.     this.contextPopupShowing = contextPopupShowing;
  78.     this.openNoteWindow = openNoteWindow;
  79.     this.newNote = newNote;
  80.     this.addTextToNote = addTextToNote;
  81.     this.addItemFromPage = addItemFromPage;
  82.     this.addAttachmentFromDialog = addAttachmentFromDialog;
  83.     this.addAttachmentFromPage = addAttachmentFromPage;
  84.     this.viewAttachment = viewAttachment;
  85.     this.viewSelectedAttachment = viewSelectedAttachment;
  86.     this.showSelectedAttachmentInFilesystem = showSelectedAttachmentInFilesystem;
  87.     this.showAttachmentNotFoundDialog = showAttachmentNotFoundDialog;
  88.     this.relinkAttachment = relinkAttachment;
  89.     this.reportErrors = reportErrors;
  90.     this.displayErrorMessage = displayErrorMessage;
  91.     
  92.     const DEFAULT_ZPANE_HEIGHT = 300;
  93.     const COLLECTIONS_HEIGHT = 125; // minimum height of the collections pane and toolbar
  94.     
  95.     var self = this;
  96.     
  97.     /*
  98.      * Called when the window is open
  99.      */
  100.     function onLoad()
  101.     {
  102.         if (!Zotero || !Zotero.initialized) {
  103.             return;
  104.         }
  105.         
  106.         if(Zotero.Prefs.get("zoteroPaneOnTop"))
  107.         {
  108.             var oldPane = document.getElementById('zotero-pane');
  109.             var oldSplitter = document.getElementById('zotero-splitter');
  110.             var appContent = document.getElementById('appcontent');
  111.             
  112.             var newPane = document.createElement('hbox');
  113.             newPane.setAttribute('id','zotero-pane');
  114.             newPane.setAttribute('persist','savedHeight');
  115.             newPane.setAttribute('hidden', true);
  116.             newPane.setAttribute('onkeydown', 'ZoteroPane.handleKeyDown(event, this.id)');
  117.             newPane.setAttribute('onkeyup', 'ZoteroPane.handleKeyUp(event, this.id)');
  118.             newPane.setAttribute('chromedir', '&locale.dir;');
  119.             
  120.             newPane.height = oldPane.height;
  121.             while(oldPane.hasChildNodes())
  122.                 newPane.appendChild(oldPane.firstChild);
  123.             appContent.removeChild(oldPane);
  124.             appContent.insertBefore(newPane, document.getElementById('content'));
  125.             
  126.             var newSplitter = document.createElement('splitter');
  127.             newSplitter.setAttribute('id','zotero-splitter');
  128.             newSplitter.setAttribute('hidden', true);
  129.             newSplitter.setAttribute('resizebefore','closest');
  130.             newSplitter.setAttribute('resizeafter','closest');
  131.             newSplitter.setAttribute('onmouseup', 'ZoteroPane.updateTagSelectorSize()');
  132.             appContent.removeChild(oldSplitter);
  133.             appContent.insertBefore(newSplitter, document.getElementById('content'));
  134.             
  135.             document.getElementById('zotero-tb-fullscreen').setAttribute('zoterotop','true');
  136.         }
  137.         
  138.         Zotero.setFontSize(document.getElementById('zotero-pane'))
  139.         
  140.         //Initialize collections view
  141.         this.collectionsView = new Zotero.CollectionTreeView();
  142.         var collectionsTree = document.getElementById('zotero-collections-tree');
  143.         collectionsTree.view = this.collectionsView;
  144.         collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
  145.         
  146.         var itemsTree = document.getElementById('zotero-items-tree');
  147.         itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree));
  148.         
  149.         this.updateTagSelectorSize();
  150.         
  151.         // Create the New Item (+) menu with each item type
  152.         var addMenu = document.getElementById('zotero-tb-add').firstChild;
  153.         var separator = document.getElementById('zotero-tb-add').firstChild.firstChild;
  154.         var moreMenu = document.getElementById('zotero-tb-add-more');
  155.         var itemTypes = Zotero.ItemTypes.getPrimaryTypes();
  156.         for(var i = 0; i<itemTypes.length; i++)
  157.         {
  158.             var menuitem = document.createElement("menuitem");
  159.             menuitem.setAttribute("label", Zotero.getString("itemTypes."+itemTypes[i]['name']));
  160.             menuitem.setAttribute("oncommand","ZoteroPane.newItem("+itemTypes[i]['id']+")");
  161.             menuitem.setAttribute("tooltiptext", "");
  162.             addMenu.insertBefore(menuitem, separator);
  163.         }
  164.         // Create submenu for secondary item types
  165.         var itemTypes = Zotero.ItemTypes.getSecondaryTypes();
  166.         for(var i = 0; i<itemTypes.length; i++)
  167.         {
  168.             var menuitem = document.createElement("menuitem");
  169.             menuitem.setAttribute("label", Zotero.getString("itemTypes."+itemTypes[i]['name']));
  170.             menuitem.setAttribute("oncommand","ZoteroPane.newItem("+itemTypes[i]['id']+")");
  171.             menuitem.setAttribute("tooltiptext", "");
  172.             moreMenu.appendChild(menuitem);
  173.         }
  174.         
  175.         var menu = document.getElementById("contentAreaContextMenu");
  176.         menu.addEventListener("popupshowing", ZoteroPane.contextPopupShowing, false);
  177.         
  178.         Zotero.Keys.windowInit(document);
  179.         
  180.         // If the database was initialized and Zotero hasn't been run before
  181.         // in this profile, display the Quick Start Guide -- this way the guide
  182.         // won't be displayed they sync their DB to another profile or if
  183.         // they the DB is initialized erroneously (e.g. while switching data
  184.         // directory locations)
  185.         if (Zotero.Schema.dbInitialized && Zotero.Prefs.get('firstRun')) {
  186.             setTimeout(function () {
  187.                 gBrowser.selectedTab = gBrowser.addTab('http://www.zotero.org/documentation/quick_start_guide');
  188.             }, 400);
  189.             Zotero.Prefs.set('firstRun', false);
  190.         }
  191.     }
  192.     
  193.     
  194.     /*
  195.      * Called when the window closes
  196.      */
  197.     function onUnload()
  198.     {
  199.         if (!Zotero || !Zotero.initialized) {
  200.             return;
  201.         }
  202.         
  203.         var tagSelector = document.getElementById('zotero-tag-selector');
  204.         tagSelector.unregister();
  205.         
  206.         this.collectionsView.unregister();
  207.         if (this.itemsView)
  208.             this.itemsView.unregister();
  209.     }
  210.  
  211.     /*
  212.      * Hides/displays the Zotero interface
  213.      */
  214.     function toggleDisplay()
  215.     {
  216.         var zoteroPane = document.getElementById('zotero-pane');
  217.         var zoteroSplitter = document.getElementById('zotero-splitter')
  218.         
  219.         if (zoteroPane.getAttribute('hidden') == 'true') {
  220.             var isHidden = true;
  221.         }
  222.         else if (zoteroPane.getAttribute('collapsed') == 'true') {
  223.             var isCollapsed = true;
  224.         }
  225.         
  226.         if (isHidden || isCollapsed) {
  227.             var makeVisible = true;
  228.         }
  229.         
  230.         // If Zotero not initialized, try to get the error handler
  231.         // or load the default error page
  232.         if (makeVisible && (!Zotero || !Zotero.initialized)) {
  233.             if (Zotero) {
  234.                 var errMsg = Zotero.startupError;
  235.                 var errFunc = Zotero.startupErrorHandler;
  236.             }
  237.             
  238.             if (!errMsg) {
  239.                 // Get the stringbundle manually
  240.                 var src = 'chrome://zotero/locale/zotero.properties';
  241.                 var localeService = Components.classes['@mozilla.org/intl/nslocaleservice;1'].
  242.                         getService(Components.interfaces.nsILocaleService);
  243.                 var appLocale = localeService.getApplicationLocale();
  244.                 var stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
  245.                     .getService(Components.interfaces.nsIStringBundleService);
  246.                 var stringBundle = stringBundleService.createBundle(src, appLocale);
  247.                 
  248.                 var errMsg = stringBundle.GetStringFromName('startupError');
  249.             }
  250.             
  251.             if (errFunc) {
  252.                 errFunc();
  253.             }
  254.             else {
  255.                 // TODO: Add a better error page/window here with reporting
  256.                 // instructions
  257.                 // window.loadURI('chrome://zotero/content/error.xul');
  258.                 alert(errMsg);
  259.             }
  260.             
  261.             return;
  262.         }
  263.         
  264.         zoteroSplitter.setAttribute('hidden', !makeVisible);
  265.         
  266.         // Restore fullscreen mode if necessary
  267.         var fullScreenMode = document.getElementById('zotero-tb-fullscreen').getAttribute('fullscreenmode') == 'true';
  268.         if (makeVisible && fullScreenMode) {
  269.             this.fullScreen(true);
  270.         }
  271.         
  272.         if (zoteroPane.hasAttribute('savedHeight')) {
  273.             var savedHeight = zoteroPane.getAttribute('savedHeight');
  274.         }
  275.         else {
  276.             var savedHeight = DEFAULT_ZPANE_HEIGHT;
  277.         }
  278.         
  279.         /*
  280.         Zotero.debug("zoteroPane.boxObject.height: " + zoteroPane.boxObject.height);
  281.         Zotero.debug("zoteroPane.getAttribute('height'): " + zoteroPane.getAttribute('height'));
  282.         Zotero.debug("zoteroPane.getAttribute('minheight'): " + zoteroPane.getAttribute('minheight'));
  283.         Zotero.debug("savedHeight: " + savedHeight);
  284.         */
  285.         
  286.         if (makeVisible) {
  287.             this.updateTagSelectorSize();
  288.             
  289.             var max = document.getElementById('appcontent').boxObject.height
  290.                         - zoteroSplitter.boxObject.height;
  291.             
  292.             if (isHidden) {
  293.                 zoteroPane.setAttribute('height', Math.min(savedHeight, max));
  294.                 zoteroPane.setAttribute('hidden', false);
  295.             }
  296.             else if (isCollapsed) {
  297.                 zoteroPane.setAttribute('height', Math.min(savedHeight, max));
  298.                 zoteroPane.setAttribute('collapsed', false);
  299.             }
  300.             
  301.             // Focus the quicksearch on pane open
  302.             setTimeout("document.getElementById('zotero-tb-search').inputField.select();", 1);
  303.         }
  304.         else {
  305.             zoteroPane.setAttribute('collapsed', true);
  306.             zoteroPane.height = 0;
  307.             
  308.             document.getElementById('content').setAttribute('collapsed', false);
  309.             
  310.             // Return focus to the browser content pane
  311.             window.content.window.focus();
  312.         }
  313.     }
  314.     
  315.     
  316.     function isShowing() {
  317.         var zoteroPane = document.getElementById('zotero-pane');
  318.         return zoteroPane.getAttribute('hidden') != 'true' &&
  319.                 zoteroPane.getAttribute('collapsed') != 'true';
  320.     }
  321.     
  322.     
  323.     function fullScreen(set)
  324.     {
  325.         var fs = document.getElementById('zotero-tb-fullscreen');
  326.         
  327.         if (set != undefined) {
  328.             var makeFullScreen = set;
  329.         }
  330.         else {
  331.             var makeFullScreen = fs.getAttribute('fullscreenmode') != 'true';
  332.         }
  333.         
  334.         // Turn Z-pane flex on to stretch to window in full-screen, but off otherwise so persist works
  335.         document.getElementById('zotero-pane').setAttribute('flex', makeFullScreen ? "1" : "0");
  336.         document.getElementById('content').setAttribute('collapsed', makeFullScreen);
  337.         document.getElementById('zotero-splitter').setAttribute('hidden', makeFullScreen);
  338.         fs.setAttribute('fullscreenmode', makeFullScreen);
  339.     }
  340.     
  341.     
  342.     function isFullScreen() {
  343.         var fs = document.getElementById('zotero-tb-fullscreen');
  344.         return fs.getAttribute('fullscreenmode') == 'true'
  345.     }
  346.     
  347.     
  348.     /*
  349.      * Trigger actions based on keyboard shortcuts
  350.      */
  351.     function handleKeyDown(event, from) {
  352.         if (from == 'zotero-pane') {
  353.             // Highlight collections containing selected items
  354.             //
  355.             // We use Control (17) on Windows because Alt triggers the menubar;
  356.             //     otherwise we use Alt/Option (18)
  357.             if ((Zotero.isWin && event.keyCode == 17 && !event.altKey) ||
  358.                     (!Zotero.isWin && event.keyCode == 18 && !event.ctrlKey)
  359.                     && !event.shiftKey && !event.metaKey) {
  360.                 
  361.                 this.highlightTimer = Components.classes["@mozilla.org/timer;1"].
  362.                     createInstance(Components.interfaces.nsITimer);
  363.                 // {} implements nsITimerCallback
  364.                 this.highlightTimer.initWithCallback({
  365.                     notify: ZoteroPane.setHighlightedRowsCallback
  366.                 }, 225, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  367.             }
  368.             else if ((Zotero.isWin && event.ctrlKey) ||
  369.                     (!Zotero.isWin && event.altKey)) {
  370.                 if (this.highlightTimer) {
  371.                     this.highlightTimer.cancel();
  372.                     this.highlightTimer = null;
  373.                 }
  374.                 ZoteroPane.collectionsView.setHighlightedRows();
  375.             }
  376.             
  377.             return;
  378.         }
  379.         
  380.         // Ignore keystrokes if Zotero pane is closed
  381.         var zoteroPane = document.getElementById('zotero-pane');
  382.         if (zoteroPane.getAttribute('hidden') == 'true' ||
  383.                 zoteroPane.getAttribute('collapsed') == 'true') {
  384.             return;
  385.         }
  386.         
  387.         var useShift = Zotero.isMac;
  388.         
  389.         var key = String.fromCharCode(event.which);
  390.         if (!key) {
  391.             Zotero.debug('No key');
  392.             return;
  393.         }
  394.         
  395.         // Ignore modifiers other than accel-alt (or accel-shift if useShift is on)
  396.         if (!((Zotero.isMac ? event.metaKey : event.ctrlKey) &&
  397.                 useShift ? event.shiftKey : event.altKey)) {
  398.             return;
  399.         }
  400.         
  401.         var command = Zotero.Keys.getCommand(key);
  402.         if (!command) {
  403.             return;
  404.         }
  405.         
  406.         Zotero.debug(command);
  407.         
  408.         switch (command) {
  409.             case 'library':
  410.                 document.getElementById('zotero-collections-tree').focus();
  411.                 ZoteroPane.collectionsView.selection.select(0);
  412.                 break;
  413.             case 'quicksearch':
  414.                 document.getElementById('zotero-tb-search').select();
  415.                 break;
  416.             case 'newItem':
  417.                 ZoteroPane.newItem(2); // book
  418.                 document.getElementById('zotero-editpane-type-menu').focus();
  419.                 break;
  420.             case 'newNote':
  421.                 // Use key that's not the modifier as the popup toggle
  422.                 ZoteroPane.newNote(useShift ? event.altKey : event.shiftKey);
  423.                 break;
  424.             case 'toggleTagSelector':
  425.                 ZoteroPane.toggleTagSelector();
  426.                 break;
  427.             case 'toggleFullscreen':
  428.                 ZoteroPane.fullScreen();
  429.                 break;
  430.             case 'copySelectedItemCitationsToClipboard':
  431.                 ZoteroPane.copySelectedItemsToClipboard(true)
  432.                 break;
  433.             case 'copySelectedItemsToClipboard':
  434.                 ZoteroPane.copySelectedItemsToClipboard();
  435.                 break;
  436.             default:
  437.                 throw ('Command "' + command + '" not found in ZoteroPane.handleKeyDown()');
  438.         }
  439.         
  440.         event.preventDefault();
  441.     }
  442.     
  443.     
  444.     function handleKeyUp(event, from) {
  445.         if (from == 'zotero-pane') {
  446.             if ((Zotero.isWin && event.keyCode == 17) ||
  447.                     (!Zotero.isWin && event.keyCode == 18)) {
  448.                 if (this.highlightTimer) {
  449.                     this.highlightTimer.cancel();
  450.                     this.highlightTimer = null;
  451.                 }
  452.                 ZoteroPane.collectionsView.setHighlightedRows();
  453.             }
  454.         }
  455.     }
  456.     
  457.     
  458.     /*
  459.      * Highlights collections containing selected items on Ctrl (Win) or
  460.      * Option/Alt (Mac/Linux) press
  461.      */
  462.     function setHighlightedRowsCallback() {
  463.         var itemIDs = ZoteroPane.getSelectedItems(true);
  464.         if (itemIDs && itemIDs.length) {
  465.             var collectionIDs = Zotero.Collections.getCollectionsContainingItems(itemIDs, true);
  466.             if (collectionIDs) {
  467.                 ZoteroPane.collectionsView.setHighlightedRows(collectionIDs);
  468.             }
  469.         }
  470.     }
  471.     
  472.     
  473.     function handleKeyPress(event, from) {
  474.         if (from == 'zotero-collections-tree') {
  475.             if (event.keyCode == event.DOM_VK_BACK_SPACE ||
  476.                     event.keyCode == event.DOM_VK_DELETE) {
  477.                 ZoteroPane.deleteSelectedCollection();
  478.                 event.preventDefault();
  479.                 return;
  480.             }
  481.         }
  482.         else if (from == 'zotero-items-tree') {
  483.             if (event.keyCode == event.DOM_VK_BACK_SPACE ||
  484.                     event.keyCode == event.DOM_VK_DELETE) {
  485.                 // If Cmd or Ctrl delete, delete from Library (with prompt)
  486.                 var fromDB = event.metaKey || (!Zotero.isMac && event.ctrlKey);
  487.                 ZoteroPane.deleteSelectedItem(fromDB);
  488.                 event.preventDefault();
  489.                 return;
  490.             }
  491.         }
  492.     }
  493.     
  494.     
  495.     /*
  496.      * Create a new item
  497.      *
  498.      * _data_ is an optional object with field:value for itemData
  499.      */
  500.     function newItem(typeID, data)
  501.     {
  502.         if (!Zotero.stateCheck()) {
  503.             this.displayErrorMessage(true);
  504.             return false;
  505.         }
  506.         
  507.         var item = new Zotero.Item(typeID);
  508.         
  509.         for (var i in data)
  510.         {
  511.             item.setField(i, data[i]);
  512.         }
  513.         
  514.         item.save();
  515.         
  516.         if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
  517.             this.itemsView._itemGroup.ref.addItem(item.getID());
  518.         }
  519.         
  520.         //set to Info tab
  521.         document.getElementById('zotero-view-item').selectedIndex = 0;
  522.         
  523.         this.selectItem(item.getID());
  524.         
  525.         return item;
  526.     }
  527.     
  528.     function newCollection(parent)
  529.     {
  530.         var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  531.                                 .getService(Components.interfaces.nsIPromptService);
  532.         
  533.         var untitled = Zotero.DB.getNextName('collections', 'collectionName',
  534.             Zotero.getString('pane.collections.untitled'));
  535.         
  536.         var newName = { value: untitled };
  537.         var result = promptService.prompt(window,
  538.             Zotero.getString('pane.collections.newCollection'),
  539.             Zotero.getString('pane.collections.name'), newName, "", {});
  540.         
  541.         if (!result)
  542.         {
  543.             return;
  544.         }
  545.         
  546.         if (!newName.value)
  547.         {
  548.             newName.value = untitled;
  549.         }
  550.         
  551.         Zotero.Collections.add(newName.value, parent);
  552.     }
  553.     
  554.     function newSearch()
  555.     {
  556.         var s = new Zotero.Search();
  557.         s.addCondition('title', 'contains', '');
  558.         
  559.         var untitled = Zotero.getString('pane.collections.untitled');
  560.         untitled = Zotero.DB.getNextName('savedSearches', 'savedSearchName',
  561.             Zotero.getString('pane.collections.untitled'));
  562.         var io = {dataIn: {search: s, name: untitled}, dataOut: null};
  563.         window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
  564.     }
  565.     
  566.     
  567.     function openAdvancedSearchWindow() {
  568.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  569.                     .getService(Components.interfaces.nsIWindowMediator);
  570.         var enumerator = wm.getEnumerator('zotero:search');
  571.         while (enumerator.hasMoreElements()) {
  572.             var win = enumerator.getNext();
  573.         }
  574.         
  575.         if (win) {
  576.             win.focus();
  577.             return;
  578.         }
  579.         
  580.         var s = new Zotero.Search();
  581.         s.addCondition('title', 'contains', '');
  582.         var io = {dataIn: {search: s}, dataOut: null};
  583.         window.openDialog('chrome://zotero/content/advancedSearch.xul', '', 'chrome,dialog=no,centerscreen', io);
  584.     }
  585.     
  586.     
  587.     function toggleTagSelector(){
  588.         var zoteroPane = document.getElementById('zotero-pane');
  589.         var splitter = document.getElementById('zotero-tags-splitter');
  590.         var tagSelector = document.getElementById('zotero-tag-selector');
  591.         
  592.         var showing = tagSelector.getAttribute('collapsed') == 'true';
  593.         tagSelector.setAttribute('collapsed', !showing);
  594.         splitter.setAttribute('collapsed', !showing);
  595.         this.updateTagSelectorSize();
  596.         
  597.         // If showing, set scope to items in current view
  598.         // and focus filter textbox
  599.         if (showing) {
  600.             _setTagScope();
  601.             tagSelector.focusTextbox();
  602.         }
  603.         // If hiding, clear selection
  604.         else {
  605.             tagSelector.uninit();
  606.         }
  607.     }
  608.     
  609.     
  610.     function updateTagSelectorSize() {
  611.         //Zotero.debug('Updating tag selector size');
  612.         var zoteroPane = document.getElementById('zotero-pane');
  613.         var splitter = document.getElementById('zotero-tags-splitter');
  614.         var tagSelector = document.getElementById('zotero-tag-selector');
  615.         
  616.         // Nothing should be bigger than appcontent's height
  617.         var max = document.getElementById('appcontent').boxObject.height
  618.                     - splitter.boxObject.height;
  619.         
  620.         // Shrink tag selector to appcontent's height
  621.         var maxTS = max - COLLECTIONS_HEIGHT;
  622.         if (parseInt(tagSelector.getAttribute("height")) > maxTS) {
  623.             //Zotero.debug("Limiting tag selector height to appcontent");
  624.             tagSelector.setAttribute('height', maxTS);
  625.         }
  626.         
  627.         height = tagSelector.boxObject.height;
  628.         
  629.         /*
  630.         Zotero.debug("tagSelector.boxObject.height: " + tagSelector.boxObject.height);
  631.         Zotero.debug("tagSelector.getAttribute('height'): " + tagSelector.getAttribute('height'));
  632.         Zotero.debug("zoteroPane.boxObject.height: " + zoteroPane.boxObject.height);
  633.         Zotero.debug("zoteroPane.getAttribute('height'): " + zoteroPane.getAttribute('height'));
  634.         */
  635.         
  636.         // Don't let the Z-pane jump back down to its previous height
  637.         // (if shrinking or hiding the tag selector let it clear the min-height)
  638.         if (zoteroPane.getAttribute('height') < zoteroPane.boxObject.height) {
  639.             //Zotero.debug("Setting Zotero pane height attribute to " +  zoteroPane.boxObject.height);
  640.             zoteroPane.setAttribute('height', zoteroPane.boxObject.height);
  641.         }
  642.         
  643.         if (tagSelector.getAttribute('collapsed') == 'true') {
  644.             // 32px is the default Z pane min-height in overlay.css
  645.             height = 32;
  646.         }
  647.         else {
  648.             // tS.boxObject.height doesn't exist at startup, so get from attribute
  649.             if (!height) {
  650.                 height = parseInt(tagSelector.getAttribute('height'));
  651.             }
  652.             // 121px seems to be enough room for the toolbar and collections
  653.             // tree at minimum height
  654.             height = height + 125;
  655.         }
  656.         
  657.         //Zotero.debug('Setting Zotero pane minheight to ' + height);
  658.         zoteroPane.setAttribute('minheight', height);
  659.         
  660.         if (this.isShowing() && !this.isFullScreen()) {
  661.             zoteroPane.setAttribute('savedHeight', zoteroPane.boxObject.height);
  662.         }
  663.         
  664.         // Fix bug whereby resizing the Z pane downward after resizing
  665.         // the tag selector up and then down sometimes caused the Z pane to
  666.         // stay at a fixed size and get pushed below the bottom
  667.         tagSelector.height++;
  668.         tagSelector.height--;
  669.     }
  670.     
  671.     
  672.     function getTagSelection(){
  673.         var tagSelector = document.getElementById('zotero-tag-selector');
  674.         return tagSelector.selection ? tagSelector.selection : {};
  675.     }
  676.     
  677.     
  678.     function clearTagSelection() {
  679.         if (Zotero.hasValues(this.getTagSelection())) {
  680.             var tagSelector = document.getElementById('zotero-tag-selector');
  681.             tagSelector.clearAll();
  682.         }
  683.     }
  684.     
  685.     
  686.     /*
  687.      * Sets the tag filter on the items view
  688.      */
  689.     function updateTagFilter(){
  690.         this.itemsView.setFilter('tags', getTagSelection());
  691.     }
  692.     
  693.     
  694.     /*
  695.      * Set the tags scope to the items in the current view
  696.      *
  697.      * Passed to the items tree to trigger on changes
  698.      */
  699.     function _setTagScope() {
  700.         var itemgroup = self.collectionsView.
  701.             _getItemAtRow(self.collectionsView.selection.currentIndex);
  702.         var tagSelector = document.getElementById('zotero-tag-selector');
  703.         if (!tagSelector.getAttribute('collapsed') ||
  704.                 tagSelector.getAttribute('collapsed') == 'false') {
  705.             Zotero.debug('Updating tag selector with current tags');
  706.             tagSelector.scope = itemgroup.getChildTags();
  707.         }
  708.     }
  709.     
  710.     
  711.     function onCollectionSelected()
  712.     {
  713.         if (this.itemsView)
  714.         {
  715.             this.itemsView.unregister();
  716.         }
  717.         
  718.         document.getElementById('zotero-tb-search').value = ""; 
  719.         
  720.         if (this.collectionsView.selection.count == 1 && this.collectionsView.selection.currentIndex != -1) {
  721.             var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
  722.             itemgroup.setSearch('');
  723.             itemgroup.setTags(getTagSelection());
  724.             
  725.             try {
  726.                 Zotero.UnresponsiveScriptIndicator.disable();
  727.                 this.itemsView = new Zotero.ItemTreeView(itemgroup);
  728.                 this.itemsView.addCallback(_setTagScope);
  729.                 document.getElementById('zotero-items-tree').view = this.itemsView;
  730.                 this.itemsView.selection.clearSelection();
  731.             }
  732.             finally {
  733.                 Zotero.UnresponsiveScriptIndicator.enable();
  734.             }
  735.             
  736.             if (itemgroup.isLibrary()) {
  737.                 Zotero.Prefs.set('lastViewedFolder', 'L');
  738.             }
  739.             if (itemgroup.isCollection()) {
  740.                 Zotero.Prefs.set('lastViewedFolder', 'C' + itemgroup.ref.getID());
  741.             }
  742.             else if (itemgroup.isSearch()) {
  743.                 Zotero.Prefs.set('lastViewedFolder', 'S' + itemgroup.ref.id);
  744.             }
  745.         }
  746.         else
  747.         {
  748.             document.getElementById('zotero-items-tree').view = this.itemsView = null;
  749.         }
  750.     }
  751.     
  752.     function itemSelected()
  753.     {
  754.         if (!Zotero.stateCheck()) {
  755.             this.displayErrorMessage();
  756.             return;
  757.         }
  758.         
  759.         if (this.itemsView && this.itemsView.selection.count == 1 && this.itemsView.selection.currentIndex != -1)
  760.         {
  761.             var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
  762.             
  763.             if(item.isNote())
  764.             {
  765.                 var noteEditor = document.getElementById('zotero-note-editor');
  766.                 
  767.                 // If loading new or different note, disable undo while we repopulate the text field
  768.                 // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
  769.                 // undo content from another note into the current one.
  770.                 if (!noteEditor.note || noteEditor.note.getID() != item.ref.getID()) {
  771.                     noteEditor.id('noteField').editor.enableUndo(false);
  772.                 }
  773.                 noteEditor.item = null;
  774.                 noteEditor.note = item.ref;
  775.                 
  776.                 noteEditor.id('noteField').editor.enableUndo(true);
  777.                 
  778.                 document.getElementById('zotero-view-note-button').setAttribute('noteID',item.ref.getID());
  779.                 if(item.ref.getSource())
  780.                 {
  781.                     document.getElementById('zotero-view-note-button').setAttribute('sourceID',item.ref.getSource());
  782.                 }
  783.                 else
  784.                 {
  785.                     document.getElementById('zotero-view-note-button').removeAttribute('sourceID');
  786.                 }
  787.                 document.getElementById('zotero-item-pane-content').selectedIndex = 2;
  788.             }
  789.             else if(item.isAttachment())
  790.             {
  791.                 // DEBUG: this is annoying -- we really want to use an abstracted
  792.                 // version of createValueElement() from itemPane.js
  793.                 // (ideally in an XBL binding)
  794.                 
  795.                 // Wrap title to multiple lines if necessary
  796.                 var label = document.getElementById('zotero-attachment-label');
  797.                 while (label.hasChildNodes())
  798.                 {
  799.                     label.removeChild(label.firstChild);
  800.                 }
  801.                 var val = item.getField('title');
  802.                 
  803.                 var firstSpace = val.indexOf(" ");
  804.                 // Crop long uninterrupted text
  805.                 if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29)
  806.                 {
  807.                     label.setAttribute('crop', 'end');
  808.                     label.setAttribute('value', val);
  809.                 }
  810.                 // Create a <description> element, essentially
  811.                 else
  812.                 {
  813.                     label.appendChild(document.createTextNode(val));
  814.                 }
  815.                 
  816.                 // For the time being, use a silly little popup
  817.                 label.className = 'zotero-clicky';
  818.                 label.onclick = function(event){
  819.                     var nsIPS = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  820.                             .getService(Components.interfaces.nsIPromptService);
  821.                     
  822.                     var newTitle = { value: val };
  823.                     var checkState = { value: Zotero.Prefs.get('lastRenameAssociatedFile') };
  824.                     
  825.                     while (true) {
  826.                         var result = nsIPS.prompt(window,
  827.                             Zotero.getString('pane.item.attachments.rename.title'),
  828.                             '', newTitle,
  829.                             Zotero.getString('pane.item.attachments.rename.renameAssociatedFile'),
  830.                             checkState);
  831.                         
  832.                         // If they hit cancel or left it blank
  833.                         if (!result || !newTitle.value) {
  834.                             return;
  835.                         }
  836.                         
  837.                         Zotero.Prefs.set('lastRenameAssociatedFile', checkState.value);
  838.                         
  839.                         // Rename associated file
  840.                         if (checkState.value) {
  841.                             var renamed = item.ref.renameAttachmentFile(newTitle.value);
  842.                             if (renamed == -1) {
  843.                                 var confirmed = confirm(newTitle.value + ' exists. Overwrite existing file?');
  844.                                 if (confirmed) {
  845.                                     item.ref.renameAttachmentFile(newTitle.value, true);
  846.                                     break;
  847.                                 }
  848.                                 // If they said not to overwrite existing file,
  849.                                 // start again
  850.                                 continue;
  851.                             }
  852.                             else if (renamed == -2 || !renamed) {
  853.                                 alert(Zotero.getString('pane.item.attachments.rename.error'));
  854.                                 return;
  855.                             }
  856.                         }
  857.                         
  858.                         break;
  859.                     }
  860.                     
  861.                     if (newTitle.value != val) {
  862.                         item.ref.setField('title', newTitle.value);
  863.                         item.ref.save();
  864.                     }
  865.                 }
  866.                 
  867.                 
  868.                 var isImportedURL = item.ref.getAttachmentLinkMode() == Zotero.Attachments.LINK_MODE_IMPORTED_URL;
  869.                 
  870.                 // Metadata for URL's
  871.                 if (item.ref.getAttachmentLinkMode() == Zotero.Attachments.LINK_MODE_LINKED_URL
  872.                     || isImportedURL)
  873.                 {
  874.                     // "View Page"/"View Snapshot" label
  875.                     if (isImportedURL)
  876.                     {
  877.                         var str = Zotero.getString('pane.item.attachments.view.snapshot');
  878.                     }
  879.                     else
  880.                     {
  881.                         var str = Zotero.getString('pane.item.attachments.view.link');
  882.                     }
  883.                     
  884.                     document.getElementById('zotero-attachment-show').setAttribute('hidden', !isImportedURL);
  885.                     
  886.                     // URL
  887.                     document.getElementById('zotero-attachment-url').setAttribute('value', item.getField('url'));
  888.                     document.getElementById('zotero-attachment-url').setAttribute('hidden', false);
  889.                     // Access date
  890.                     document.getElementById('zotero-attachment-accessed').setAttribute('value',
  891.                         Zotero.getString('itemFields.accessDate') + ': '
  892.                         + Zotero.Date.sqlToDate(item.getField('accessDate'), true).toLocaleString());
  893.                     document.getElementById('zotero-attachment-accessed').setAttribute('hidden', false);
  894.                 }
  895.                 // Metadata for files
  896.                 else
  897.                 {
  898.                     var str = Zotero.getString('pane.item.attachments.view.file');
  899.                     document.getElementById('zotero-attachment-show').setAttribute('hidden', false);
  900.                     document.getElementById('zotero-attachment-url').setAttribute('hidden', true);
  901.                     document.getElementById('zotero-attachment-accessed').setAttribute('hidden', true);
  902.                 }
  903.                 
  904.                 document.getElementById('zotero-attachment-view').setAttribute('label', str);
  905.                 
  906.                 // Display page count
  907.                 var pages = Zotero.Fulltext.getPages(item.ref.getID());
  908.                 var pages = pages ? pages.total : null;
  909.                 var pagesRow = document.getElementById('zotero-attachment-pages');
  910.                 if (pages) {
  911.                     var str = Zotero.getString('itemFields.pages') + ': ' + pages;
  912.                     pagesRow.setAttribute('value', str);
  913.                     pagesRow.setAttribute('hidden', false);
  914.                 }
  915.                 else {
  916.                     pagesRow.setAttribute('hidden', true);
  917.                 }
  918.                 
  919.                 this.updateItemIndexedState();
  920.                 
  921.                 var noteEditor = document.getElementById('zotero-attachment-note-editor');
  922.                 noteEditor.item = null;
  923.                 noteEditor.note = item.ref;
  924.                 
  925.                 document.getElementById('zotero-item-pane-content').selectedIndex = 3;
  926.             }
  927.             else
  928.             {
  929.                 ZoteroItemPane.viewItem(item.ref);
  930.                 document.getElementById('zotero-item-pane-content').selectedIndex = 1;
  931.             }
  932.         }
  933.         else
  934.         {
  935.             document.getElementById('zotero-item-pane-content').selectedIndex = 0;
  936.             
  937.             var label = document.getElementById('zotero-view-selected-label');
  938.             
  939.             if (this.itemsView && this.itemsView.selection.count) {
  940.                 label.value = Zotero.getString('pane.item.selected.multiple', this.itemsView.selection.count);
  941.             }
  942.             else {
  943.                 label.value = Zotero.getString('pane.item.selected.zero');
  944.             }
  945.         }
  946.  
  947.     }
  948.     
  949.     
  950.     /*
  951.      * Update Indexed: (Yes|No|Partial) line in attachment info pane
  952.      */
  953.     function updateItemIndexedState() {
  954.         var indexBox = document.getElementById('zotero-attachment-index-box');
  955.         var indexStatus = document.getElementById('zotero-attachment-index-status');
  956.         var reindexButton = document.getElementById('zotero-attachment-reindex');
  957.         
  958.         var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex);
  959.         var status = Zotero.Fulltext.getIndexedState(item.ref.getID());
  960.         var str = 'fulltext.indexState.';
  961.         switch (status) {
  962.             case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
  963.                 str += 'unavailable';
  964.                 break;
  965.             case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
  966.                 str = 'general.no';
  967.                 break;
  968.             case Zotero.Fulltext.INDEX_STATE_PARTIAL:
  969.                 str += 'partial';
  970.                 break;
  971.             case Zotero.Fulltext.INDEX_STATE_INDEXED:
  972.                 str = 'general.yes';
  973.                 break;
  974.         }
  975.         str = Zotero.getString('fulltext.indexState.indexed') + ': ' +
  976.             Zotero.getString(str);
  977.         indexStatus.setAttribute('value', str);
  978.         
  979.         // Reindex button tooltip (string stored in zotero.properties)
  980.         var str = Zotero.getString('pane.items.menu.reindexItem');
  981.         reindexButton.setAttribute('tooltiptext', str);
  982.         
  983.         if (Zotero.Fulltext.canReindex(item.ref.getID())) {
  984.             reindexButton.setAttribute('hidden', false);
  985.         }
  986.         else {
  987.             reindexButton.setAttribute('hidden', true);
  988.         }
  989.     }
  990.     
  991.     
  992.     function reindexItem() {
  993.         var items = this.getSelectedItems();
  994.         if (!items) {
  995.             return;
  996.         }
  997.         
  998.         for (var i=0; i<items.length; i++) {
  999.             if (!items[i].isAttachment()) {
  1000.                 continue;
  1001.             }
  1002.             var itemID = items[i].getID();
  1003.             Zotero.Fulltext.indexItems(itemID, true);
  1004.         }
  1005.         this.updateItemIndexedState();
  1006.     }
  1007.     
  1008.     
  1009.     function duplicateSelectedItem() {
  1010.         var newItemID = this.getSelectedItems()[0].clone();
  1011.         var newItem = Zotero.Items.get(newItemID);
  1012.         
  1013.         if (this.itemsView._itemGroup.isCollection()) {
  1014.             this.itemsView._itemGroup.ref.addItem(newItem.getID());
  1015.             this.selectItem(newItemID);
  1016.         }
  1017.     }
  1018.     
  1019.     
  1020.     /*
  1021.      *  _force_ deletes item from DB even if removing from a collection or search
  1022.      */
  1023.     function deleteSelectedItem(force)
  1024.     {
  1025.         if (this.itemsView && this.itemsView.selection.count > 0) {
  1026.             if (!force){
  1027.                 if (this.itemsView._itemGroup.isCollection()) {
  1028.                     var noPrompt = true;
  1029.                 }
  1030.                 // Do nothing in search view
  1031.                 else if (this.itemsView._itemGroup.isSearch()) {
  1032.                     return;
  1033.                 }
  1034.             }
  1035.             
  1036.             var eraseChildren = {value: true};
  1037.             var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  1038.                                             .getService(Components.interfaces.nsIPromptService);
  1039.             var hasChildren;
  1040.             
  1041.             if (!this.getSelectedCollection()) {
  1042.                 var start = new Object();
  1043.                 var end = new Object();
  1044.                 for (var i=0, len=this.itemsView.selection.getRangeCount(); i<len; i++) {
  1045.                     this.itemsView.selection.getRangeAt(i,start,end);
  1046.                     for (var j=start.value; j<=end.value; j++)
  1047.                         if (this.itemsView._getItemAtRow(j).numChildren()) {
  1048.                             hasChildren = true;
  1049.                             break;
  1050.                         }
  1051.                 }
  1052.             }
  1053.             
  1054.             if (noPrompt || promptService.confirmCheck(
  1055.                 window,
  1056.                 Zotero.getString('pane.items.delete.title'),
  1057.                 Zotero.getString('pane.items.delete' + (this.itemsView.selection.count>1 ? '.multiple' : '')),
  1058.                 hasChildren ? Zotero.getString('pane.items.delete.attached') : '',
  1059.                 eraseChildren))
  1060.             {
  1061.                 this.itemsView.deleteSelection(eraseChildren.value, force);
  1062.             }
  1063.         }
  1064.     }
  1065.     
  1066.     function deleteSelectedCollection()
  1067.     {
  1068.         if (this.collectionsView.selection.count == 1) {
  1069.             var row =
  1070.                 this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
  1071.             
  1072.             if (row.isCollection())
  1073.             {
  1074.                 if (confirm(Zotero.getString('pane.collections.delete')))
  1075.                 {
  1076.                     this.collectionsView.deleteSelection();
  1077.                 }
  1078.             }
  1079.             else if (row.isSearch())
  1080.             {
  1081.                 if (confirm(Zotero.getString('pane.collections.deleteSearch')))
  1082.                 {
  1083.                     this.collectionsView.deleteSelection();
  1084.                 }
  1085.             }
  1086.         }
  1087.     }
  1088.     
  1089.     function editSelectedCollection()
  1090.     {
  1091.         if (this.collectionsView.selection.count > 0) {
  1092.             var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
  1093.             
  1094.             if(collection.isCollection())
  1095.             {
  1096.                 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  1097.                                         .getService(Components.interfaces.nsIPromptService);
  1098.                 
  1099.                 var newName = { value: collection.getName() };
  1100.                 var result = promptService.prompt(window, "",
  1101.                     Zotero.getString('pane.collections.rename'), newName, "", {});
  1102.                 
  1103.                 if (result && newName.value)
  1104.                 {
  1105.                     collection.ref.rename(newName.value);
  1106.                 }
  1107.             }
  1108.             else
  1109.             {
  1110.                 var s = new Zotero.Search();
  1111.                 s.load(collection.ref['id']);
  1112.                 var io = {dataIn: {search: s, name: collection.getName()}, dataOut: null};
  1113.                 window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
  1114.                 if(io.dataOut)
  1115.                     this.onCollectionSelected(); //reload itemsView
  1116.             }
  1117.         }
  1118.     }
  1119.     
  1120.     
  1121.     function copySelectedItemsToClipboard(asCitations) {
  1122.         var items = this.getSelectedItems();
  1123.         if (!items.length) {
  1124.             return;
  1125.         }
  1126.         
  1127.         // Make sure at least one item is not a standalone note or attachment
  1128.         var haveRegularItem = false;
  1129.         for each(var item in items) {
  1130.             if (item.isRegularItem()) {
  1131.                 haveRegularItem = true;
  1132.                 break;
  1133.             }
  1134.         }
  1135.         if (!haveRegularItem) {
  1136.             window.alert(Zotero.getString("fileInterface.noReferencesError"));
  1137.             return;
  1138.         }
  1139.         
  1140.         var url = window.content.location.href;
  1141.         var [mode, format] = Zotero.QuickCopy.getFormatFromURL(url).split('=');
  1142.         var [mode, contentType] = mode.split('/');
  1143.         
  1144.         if (mode == 'bibliography') {
  1145.             if (asCitations) {
  1146.                 Zotero_File_Interface.copyCitationToClipboard(items, format, contentType == 'html');
  1147.             }
  1148.             else {
  1149.                 Zotero_File_Interface.copyItemsToClipboard(items, format, contentType == 'html');
  1150.             }
  1151.         }
  1152.         else if (mode == 'export') {
  1153.             // Copy citations doesn't work in export mode
  1154.             if (asCitations) {
  1155.                 return;
  1156.             }
  1157.             else {
  1158.                 Zotero_File_Interface.exportItemsToClipboard(items, format);
  1159.             }
  1160.         }
  1161.     }
  1162.     
  1163.     
  1164.     function clearQuicksearch() {
  1165.         var search = document.getElementById('zotero-tb-search');
  1166.         if (search.value != '') {
  1167.             search.value = '';
  1168.             search.doCommand('cmd_zotero_search');
  1169.         }
  1170.     }
  1171.     
  1172.     
  1173.     function handleSearchKeypress(textbox, event) {
  1174.         // Events that turn find-as-you-type on
  1175.         if (event.keyCode == event.DOM_VK_ESCAPE) {
  1176.             textbox.value = '';
  1177.             ZoteroPane.setItemsPaneMessage(Zotero.getString('searchInProgress'));
  1178.             setTimeout("document.getElementById('zotero-tb-search').doCommand('cmd_zotero_search'); ZoteroPane.clearItemsPaneMessage();", 1);
  1179.         }
  1180.         else if (event.keyCode == event.DOM_VK_RETURN ||
  1181.             event.keyCode == event.DOM_VK_ENTER) {
  1182.             textbox.skipTimeout = true;
  1183.             ZoteroPane.setItemsPaneMessage(Zotero.getString('searchInProgress'));
  1184.             setTimeout("document.getElementById('zotero-tb-search').doCommand('cmd_zotero_search'); ZoteroPane.clearItemsPaneMessage();", 1);
  1185.         }
  1186.     }
  1187.     
  1188.     
  1189.     function handleSearchInput(textbox, event) {
  1190.         // This is the new length, except, it seems, when the change is a
  1191.         // result of Undo or Redo
  1192.         if (!textbox.value.length) {
  1193.             textbox.skipTimeout = true;
  1194.             ZoteroPane.setItemsPaneMessage(Zotero.getString('searchInProgress'));
  1195.             setTimeout("document.getElementById('zotero-tb-search').doCommand('cmd_zotero_search'); ZoteroPane.clearItemsPaneMessage();", 1);
  1196.         }
  1197.         else if (textbox.value.indexOf('"') != -1) {
  1198.             ZoteroPane.setItemsPaneMessage(Zotero.getString('advancedSearchMode'));
  1199.         }
  1200.     }
  1201.     
  1202.     
  1203.     function search()
  1204.     {
  1205.         if (this.itemsView) {
  1206.             var searchVal = document.getElementById('zotero-tb-search').value;
  1207.             this.itemsView.setFilter('search', searchVal);
  1208.             
  1209.             document.getElementById('zotero-tb-search-cancel').hidden = searchVal == "";
  1210.         }
  1211.         
  1212.     }
  1213.     
  1214.     
  1215.     /*
  1216.      * Select item in current collection or, if not there, in Library
  1217.      *
  1218.      * If _inLibrary_, force switch to Library
  1219.      * If _expand_, open item if it's a container
  1220.      */
  1221.     function selectItem(itemID, inLibrary, expand)
  1222.     {
  1223.         if (!itemID) {
  1224.             return;
  1225.         }
  1226.         
  1227.         if (this.itemsView) {
  1228.             if (!this.itemsView._itemGroup.isLibrary() && inLibrary) {
  1229.                 this.collectionsView.selection.select(0);
  1230.             }
  1231.             
  1232.             var selected = this.itemsView.selectItem(itemID, expand);
  1233.             if (!selected) {
  1234.                 this.collectionsView.selection.select(0);
  1235.                 this.itemsView.selectItem(itemID, expand);
  1236.             }
  1237.         }
  1238.     }
  1239.     
  1240.     function getSelectedCollection(asID) {
  1241.         if (this.collectionsView.selection
  1242.                 && this.collectionsView.selection.count > 0
  1243.                 && this.collectionsView.selection.currentIndex != -1) {
  1244.             var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
  1245.             if (collection && collection.isCollection()) {
  1246.                 return asID ? collection.ref.getID() : collection.ref;
  1247.             }
  1248.         }
  1249.         // If the Zotero pane hasn't yet been opened, use the lastViewedFolder pref
  1250.         else {
  1251.             var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
  1252.             var matches = lastViewedFolder.match(/^(?:(C|S)([0-9]+)|L)$/);
  1253.             if (matches && matches[1] == 'C') {
  1254.                 var col = Zotero.Collections.get(matches[2]);
  1255.                 if (col) {
  1256.                     return asID ? col.getID() : col;
  1257.                 }
  1258.             }
  1259.         }
  1260.         return false;
  1261.     }
  1262.     
  1263.     function getSelectedSavedSearch(asID)
  1264.     {
  1265.         if (this.collectionsView.selection.count > 0 && this.collectionsView.selection.currentIndex != -1) {
  1266.             var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
  1267.             if (collection && collection.isSearch()) {
  1268.                 return asID ? collection.ref.id : collection.ref;
  1269.             }
  1270.         }
  1271.         // If the Zotero pane hasn't yet been opened, use the lastViewedFolder pref
  1272.         else {
  1273.             var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
  1274.             var matches = lastViewedFolder.match(/^(?:(C|S)([0-9]+)|L)$/);
  1275.             if (matches && matches[1] == 'S') {
  1276.                 var search = Zotero.Search.get(matches[2]);
  1277.                 if (search) {
  1278.                     return asID ? search.id : search;
  1279.                 }
  1280.             }
  1281.         }
  1282.         return false;
  1283.     }
  1284.     
  1285.     /*
  1286.      * Return an array of Item objects for selected items
  1287.      *
  1288.      * If asIDs is true, return an array of itemIDs instead
  1289.      */
  1290.     function getSelectedItems(asIDs)
  1291.     {
  1292.         if (this.itemsView) {
  1293.             return this.itemsView.getSelectedItems(asIDs);
  1294.         }
  1295.         return [];
  1296.     }
  1297.     
  1298.     
  1299.     /*
  1300.      * Returns an array of item ids of visible items in current sort order
  1301.      */
  1302.     function getSortedItems() {
  1303.         if (!this.itemsView) {
  1304.             return false;
  1305.         }
  1306.         
  1307.         return this.itemsView.getSortedItems();
  1308.     }
  1309.     
  1310.     
  1311.     function getSortField() {
  1312.         if (!this.itemsView) {
  1313.             return false;
  1314.         }
  1315.         
  1316.         return this.itemsView.getSortField();
  1317.     }
  1318.     
  1319.     
  1320.     function getSortDirection() {
  1321.         if (!this.itemsView) {
  1322.             return false;
  1323.         }
  1324.         
  1325.         return this.itemsView.getSortDirection();
  1326.     }
  1327.     
  1328.     
  1329.     function buildCollectionContextMenu()
  1330.     {
  1331.         var menu = document.getElementById('zotero-collectionmenu');
  1332.         
  1333.         var m = {
  1334.             newCollection: 0,
  1335.             newSavedSearch: 1,
  1336.             newSubcollection: 2,
  1337.             sep1: 3,
  1338.             editSelectedCollection: 4,
  1339.             removeCollection: 5,
  1340.             sep2: 6,
  1341.             exportCollection: 7,
  1342.             createBibCollection: 8,
  1343.             exportFile: 9,
  1344.             loadReport: 10
  1345.         };
  1346.         
  1347.         // Collection
  1348.         if (this.collectionsView.selection.count == 1 &&
  1349.             this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isCollection())
  1350.         {
  1351.             var hide = [m.newCollection, m.newSavedSearch, m.exportFile];
  1352.             var show = [m.newSubcollection, m.sep1, m.editSelectedCollection, m.removeCollection,
  1353.                 m.sep2, m.exportCollection, m.createBibCollection, m.loadReport];
  1354.             if (this.itemsView.rowCount>0) {
  1355.                 var enable = [m.exportCollection, m.createBibCollection, m.loadReport];
  1356.             }
  1357.             else
  1358.             {
  1359.                 var disable = [m.exportCollection, m.createBibCollection, m.loadReport];
  1360.             }
  1361.             
  1362.             menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.rename.collection'));
  1363.             menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.collection'));
  1364.             menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.collection'));
  1365.             menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.collection'));
  1366.             menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.collection'));
  1367.         }
  1368.         // Saved Search
  1369.         else if (this.collectionsView.selection.count == 1 &&
  1370.                 this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex).isSearch()) {
  1371.             var hide = [m.newCollection, m.newSavedSearch, m.newSubcollection, m.sep1, m.exportFile]
  1372.             var show = [m.editSelectedCollection, m.removeCollection, m.sep2, m.exportCollection,
  1373.                 m.createBibCollection, m.loadReport];
  1374.             
  1375.             if (this.itemsView.rowCount>0) {
  1376.                 var enable = [m.exportCollection, m.createBibCollection, m.loadReport];
  1377.             }
  1378.             else
  1379.             {
  1380.                 var disable = [m.exportCollection, m.createBibCollection, m.loadReport];
  1381.             }
  1382.             
  1383.             menu.childNodes[m.editSelectedCollection].setAttribute('label', Zotero.getString('pane.collections.menu.edit.savedSearch'));
  1384.             menu.childNodes[m.removeCollection].setAttribute('label', Zotero.getString('pane.collections.menu.remove.savedSearch'));
  1385.             menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.savedSearch'));
  1386.             menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch'));
  1387.             menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch'));
  1388.         }
  1389.         // Library
  1390.         else
  1391.         {
  1392.             var hide = [m.newSubcollection, m.editSelectedCollection, m.removeCollection, m.sep2,
  1393.                 m.exportCollection, m.createBibCollection, m.loadReport];
  1394.             var show = [m.newCollection, m.newSavedSearch, m.sep1, m.exportFile];
  1395.         }
  1396.         
  1397.         for (var i in disable)
  1398.         {
  1399.             menu.childNodes[disable[i]].setAttribute('disabled', true);
  1400.         }
  1401.         
  1402.         for (var i in enable)
  1403.         {
  1404.             menu.childNodes[enable[i]].setAttribute('disabled', false);
  1405.         }
  1406.         
  1407.         for (var i in hide)
  1408.         {
  1409.             menu.childNodes[hide[i]].setAttribute('hidden', true);
  1410.         }
  1411.         
  1412.         for (var i in show)
  1413.         {
  1414.             menu.childNodes[show[i]].setAttribute('hidden', false);
  1415.         }
  1416.     }
  1417.     
  1418.     function buildItemContextMenu()
  1419.     {
  1420.         var m = {
  1421.             showInLibrary: 0,
  1422.             sep1: 1,
  1423.             addNote: 2,
  1424.             attachSnapshot: 3,
  1425.             attachLink: 4,
  1426.             sep2: 5,
  1427.             duplicateItem: 6,
  1428.             deleteItem: 7,
  1429.             deleteFromLibrary: 8,
  1430.             sep3: 9,
  1431.             exportItems: 10,
  1432.             createBib: 11,
  1433.             loadReport: 12,
  1434.             sep4: 13,
  1435.             reindexItem: 14
  1436.         };
  1437.         
  1438.         var menu = document.getElementById('zotero-itemmenu');
  1439.         
  1440.         var enable = [], disable = [], show = [], hide = [], multiple = '';
  1441.         
  1442.         if (this.itemsView && this.itemsView.selection.count > 0) {
  1443.             enable.push(m.showInLibrary, m.addNote, m.attachSnapshot, m.attachLink,
  1444.                 m.sep2, m.duplicateItem, m.deleteItem, m.deleteFromLibrary,
  1445.                 m.exportItems, m.createBib, m.loadReport);
  1446.             
  1447.             // Multiple items selected
  1448.             if (this.itemsView.selection.count > 1) {
  1449.                 var multiple =  '.multiple';
  1450.                 hide.push(m.showInLibrary, m.sep1, m.addNote, m.attachSnapshot,
  1451.                     m.attachLink, m.sep2, m.duplicateItem);
  1452.                 
  1453.                 // If all items can be reindexed, show option
  1454.                 var items = this.getSelectedItems();
  1455.                 var canIndex = true;
  1456.                 for (var i=0; i<items.length; i++) {
  1457.                     if (!Zotero.Fulltext.canReindex()) {
  1458.                         canIndex = false;
  1459.                         break;
  1460.                     }
  1461.                 }
  1462.                 if (canIndex) {
  1463.                     show.push(m.sep4, m.reindexItem);
  1464.                 }
  1465.                 else {
  1466.                     hide.push(m.sep4, m.reindexItem);
  1467.                 }
  1468.             }
  1469.             // Single item selected
  1470.             else
  1471.             {
  1472.                 var item = this.itemsView._getItemAtRow(this.itemsView.selection.currentIndex).ref;
  1473.                 var itemID = item.getID();
  1474.                 menu.setAttribute('itemID', itemID);
  1475.                 
  1476.                 // Show in Library
  1477.                 if (!this.itemsView._itemGroup.isLibrary()) {
  1478.                     show.push(m.showInLibrary, m.sep1);
  1479.                 }
  1480.                 else {
  1481.                     hide.push(m.showInLibrary, m.sep1);
  1482.                 }
  1483.                 
  1484.                 if (item.isRegularItem())
  1485.                 {
  1486.                     show.push(m.addNote, m.attachSnapshot, m.attachLink, m.sep2);
  1487.                 }
  1488.                 else
  1489.                 {
  1490.                     hide.push(m.addNote, m.attachSnapshot, m.attachLink, m.sep2);
  1491.                 }
  1492.                 
  1493.                 if (item.isAttachment()) {
  1494.                     hide.push(m.duplicateItem);
  1495.                     // If not linked URL, show reindex line
  1496.                     if (Zotero.Fulltext.canReindex(item.getID())) {
  1497.                         show.push(m.sep4, m.reindexItem);
  1498.                     }
  1499.                     else {
  1500.                         hide.push(m.sep4, m.reindexItem);
  1501.                     }
  1502.                 }
  1503.                 else {
  1504.                     show.push(m.duplicateItem);
  1505.                     hide.push(m.sep4, m.reindexItem);
  1506.                 }
  1507.             }
  1508.         }
  1509.         // No items selected
  1510.         else
  1511.         {
  1512.             // Show in Library
  1513.             if (!this.itemsView._itemGroup.isLibrary()) {
  1514.                 show.push(m.showInLibrary, m.sep1);
  1515.             }
  1516.             else {
  1517.                 hide.push(m.showInLibrary, m.sep1);
  1518.             }
  1519.             
  1520.             disable.push(m.showInLibrary, m.duplicateItem, m.deleteItem,
  1521.                 m.deleteFromLibrary, m.exportItems, m.createBib, m.loadReport);
  1522.             hide.push(m.addNote, m.attachSnapshot, m.attachLink, m.sep2, m.sep4, m.reindexItem);
  1523.         }
  1524.         
  1525.         // Remove from collection
  1526.         if (this.itemsView._itemGroup.isCollection() && !(item && item.getSource()))
  1527.         {
  1528.             menu.childNodes[m.deleteItem].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple));
  1529.             show.push(m.deleteItem);
  1530.         }
  1531.         else
  1532.         {
  1533.             hide.push(m.deleteItem);
  1534.         }
  1535.         
  1536.         // Plural if necessary
  1537.         menu.childNodes[m.deleteFromLibrary].setAttribute('label', Zotero.getString('pane.items.menu.erase' + multiple));
  1538.         menu.childNodes[m.exportItems].setAttribute('label', Zotero.getString('pane.items.menu.export' + multiple));
  1539.         menu.childNodes[m.createBib].setAttribute('label', Zotero.getString('pane.items.menu.createBib' + multiple));
  1540.         menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.items.menu.generateReport' + multiple));
  1541.         menu.childNodes[m.reindexItem].setAttribute('label', Zotero.getString('pane.items.menu.reindexItem' + multiple));
  1542.         
  1543.         for (var i in disable)
  1544.         {
  1545.             menu.childNodes[disable[i]].setAttribute('disabled', true);
  1546.         }
  1547.         
  1548.         for (var i in enable)
  1549.         {
  1550.             menu.childNodes[enable[i]].setAttribute('disabled', false);
  1551.         }
  1552.         
  1553.         for (var i in hide)
  1554.         {
  1555.             menu.childNodes[hide[i]].setAttribute('hidden', true);
  1556.         }
  1557.         
  1558.         for (var i in show)
  1559.         {
  1560.             menu.childNodes[show[i]].setAttribute('hidden', false);
  1561.         }
  1562.     }
  1563.     
  1564.     
  1565.     // Adapted from: http://www.xulplanet.com/references/elemref/ref_tree.html#cmnote-9
  1566.     function onDoubleClick(event, tree)
  1567.     {
  1568.         if (event && tree && event.type == "dblclick") {
  1569.             var row = {}, col = {}, obj = {};
  1570.             tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
  1571.             
  1572.             // obj.value == 'cell'/'text'/'image'
  1573.             if (!obj.value) {
  1574.                 return;
  1575.             }
  1576.             
  1577.             if (tree.id == 'zotero-collections-tree') {
  1578.                 var s = this.getSelectedSavedSearch();
  1579.                 if (s) {
  1580.                     this.editSelectedCollection();
  1581.                 }
  1582.             }
  1583.             else if (tree.id == 'zotero-items-tree') {
  1584.                 if (this.itemsView && this.itemsView.selection.currentIndex > -1) {
  1585.                     var item = this.getSelectedItems()[0];
  1586.                     if (item && item.isNote()) {
  1587.                         document.getElementById('zotero-view-note-button').doCommand();
  1588.                     }
  1589.                     else if (item && item.isAttachment()) {
  1590.                         this.viewSelectedAttachment(event);
  1591.                     }
  1592.                 }
  1593.             }
  1594.         }
  1595.     }
  1596.     
  1597.     
  1598.     /*
  1599.      * Loads a URL following the standard modifier key behavior
  1600.      *  (e.g. meta-click == new background tab, meta-shift-click == new front tab,
  1601.      *  shift-click == new window, no modifier == frontmost tab
  1602.      */
  1603.     function loadURI(uri, event, data) {
  1604.         // Open in new tab
  1605.         if (event.metaKey || (!Zotero.isMac && event.ctrlKey)) {
  1606.             var tab = gBrowser.addTab(uri);
  1607.             var browser = gBrowser.getBrowserForTab(tab);
  1608.             
  1609.             if (data && data.attachmentID) {
  1610.                 Zotero_Browser.annotatePage(data.attachmentID, browser);
  1611.                 // In case the page has already loaded, update
  1612.                 Zotero_Browser.updateStatus();
  1613.             }
  1614.             
  1615.             if (event.shiftKey) {
  1616.                 gBrowser.selectedTab = tab;
  1617.             }
  1618.         }
  1619.         else if (event.shiftKey) {
  1620.             window.open(uri, "zotero-loaded-page",
  1621.                 "menubar=yes,location=yes,toolbar=yes,personalbar=yes,resizable=yes,scrollbars=yes,status=yes");
  1622.             
  1623.             if (data && data.attachmentID) {
  1624.                 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1625.                            .getService(Components.interfaces.nsIWindowMediator);
  1626.                 var newWindow = wm.getMostRecentWindow("navigator:browser");
  1627.                 //var browser = newWindow.getBrowser();
  1628.                 
  1629.                 newWindow.Zotero_Browser.annotatePage(data.attachmentID);
  1630.                 // In case the page has already loaded, update
  1631.                 newWindow.Zotero_Browser.updateStatus();
  1632.             }
  1633.         }
  1634.         else {
  1635.             if (data && data.attachmentID) {
  1636.                 // Enable annotation
  1637.                 Zotero_Browser.annotatePage(data.attachmentID);
  1638.             }
  1639.             window.loadURI(uri);
  1640.         }
  1641.     }
  1642.     
  1643.     
  1644.     function setItemsPaneMessage(msg, lock) {
  1645.         var elem = document.getElementById('zotero-items-pane-message-box');
  1646.         
  1647.         if (elem.getAttribute('locked') == 'true') {
  1648.             return;
  1649.         }
  1650.         
  1651.         while (elem.hasChildNodes()) {
  1652.             elem.removeChild(elem.firstChild);
  1653.         }
  1654.         var msgParts = msg.split("\n\n");
  1655.         for (var i=0; i<msgParts.length; i++) {
  1656.             var desc = document.createElement('description');
  1657.             desc.appendChild(document.createTextNode(msgParts[i]));
  1658.             elem.appendChild(desc);
  1659.         }
  1660.         
  1661.         // Make message permanent
  1662.         if (lock) {
  1663.             elem.setAttribute('locked', true);
  1664.         }
  1665.         
  1666.         document.getElementById('zotero-items-pane-content').selectedIndex = 1;
  1667.     }
  1668.     
  1669.     
  1670.     function clearItemsPaneMessage() {
  1671.         // If message box is locked, don't clear
  1672.         var box = document.getElementById('zotero-items-pane-message-box');
  1673.         if (box.getAttribute('locked') == 'true') {
  1674.             return;
  1675.         }
  1676.         
  1677.         document.getElementById('zotero-items-pane-content').selectedIndex = 0;
  1678.     }
  1679.     
  1680.     
  1681.     // Updates browser context menu options
  1682.     function contextPopupShowing()
  1683.     {
  1684.         if (!Zotero.Prefs.get('browserContentContextMenu')) {
  1685.             return;
  1686.         }
  1687.         
  1688.         var menuitem = document.getElementById("zotero-context-add-to-current-note");
  1689.         var showing = false;
  1690.         if (menuitem){
  1691.             var items = ZoteroPane.getSelectedItems();
  1692.             if (ZoteroPane.itemsView.selection && ZoteroPane.itemsView.selection.count==1
  1693.                 && items[0] && items[0].isNote()
  1694.                 && window.gContextMenu.isTextSelected)
  1695.             {
  1696.                 menuitem.hidden = false;
  1697.                 showing = true;
  1698.             }
  1699.             else
  1700.             {
  1701.                 menuitem.hidden = true;
  1702.             }
  1703.         }
  1704.         
  1705.         var menuitem = document.getElementById("zotero-context-add-to-new-note");
  1706.         if (menuitem){
  1707.             if (window.gContextMenu.isTextSelected)
  1708.             {
  1709.                 menuitem.hidden = false;
  1710.                 showing = true;
  1711.             }
  1712.             else
  1713.             {
  1714.                 menuitem.hidden = true;
  1715.             }
  1716.         }
  1717.         
  1718.         var menuitem = document.getElementById("zotero-context-save-link-as-snapshot");
  1719.         if (menuitem) {
  1720.             if (window.gContextMenu.onLink) {
  1721.                 menuitem.hidden = false;
  1722.                 showing = true;
  1723.             }
  1724.             else {
  1725.                 menuitem.hidden = true;
  1726.             }
  1727.         }
  1728.         
  1729.         var menuitem = document.getElementById("zotero-context-save-image-as-snapshot");
  1730.         if (menuitem) {
  1731.             // Not using window.gContextMenu.hasBGImage -- if the user wants it,
  1732.             // they can use the Firefox option to view and then import from there
  1733.             if (window.gContextMenu.onImage) {
  1734.                 menuitem.hidden = false;
  1735.                 showing = true;
  1736.             }
  1737.             else {
  1738.                 menuitem.hidden = true;
  1739.             }
  1740.         }
  1741.         
  1742.         var separator = document.getElementById("zotero-context-separator");
  1743.         separator.hidden = !showing;
  1744.     }
  1745.     
  1746.     
  1747.     function newNote(popup, parent, text) {
  1748.         if (!Zotero.stateCheck()) {
  1749.             this.displayErrorMessage(true);
  1750.             return;
  1751.         }
  1752.         
  1753.         if (!popup) {
  1754.             try {
  1755.                 // trim
  1756.                 text = text.replace(/^[\xA0\r\n\s]*(.*)[\xA0\r\n\s]*$/m, "$1");
  1757.             }
  1758.             catch (e){}
  1759.             
  1760.             var itemID = Zotero.Notes.add(text, parent);
  1761.             
  1762.             if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
  1763.                 this.itemsView._itemGroup.ref.addItem(itemID);
  1764.             }
  1765.             
  1766.             this.selectItem(itemID);
  1767.             
  1768.             document.getElementById('zotero-note-editor').focus();
  1769.         }
  1770.         else
  1771.         {
  1772.             // TODO: _text_
  1773.             var c = this.getSelectedCollection();
  1774.             if (c) {
  1775.                 this.openNoteWindow(null, c.getID());
  1776.             }
  1777.             else {
  1778.                 this.openNoteWindow();
  1779.             }
  1780.         }
  1781.     }
  1782.     
  1783.     function addTextToNote(text)
  1784.     {
  1785.         try {
  1786.             // trim
  1787.             text = text.replace(/^[\xA0\r\n\s]*(.*)[\xA0\r\n\s]*$/m, "$1");
  1788.         }
  1789.         catch (e){}
  1790.         
  1791.         if (!text || !text.length)
  1792.         {
  1793.             return false;
  1794.         }
  1795.         
  1796.         var items = this.getSelectedItems();
  1797.         if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) {
  1798.             var note = items[0].getNote()
  1799.             items[0].updateNote(note == '' ? text : note + "\n\n" + text);
  1800.             var noteElem = document.getElementById('zotero-note-editor')
  1801.             noteElem.focus();
  1802.             noteElem.id('noteField').inputField.editor.
  1803.                 selectionController.scrollSelectionIntoView(1,
  1804.                     1,
  1805.                     true);
  1806.             return true;
  1807.         }
  1808.         
  1809.         return false;
  1810.     }
  1811.     
  1812.     function openNoteWindow(itemID, col, parentItemID)
  1813.     {
  1814.         var name = null;
  1815.         
  1816.         if (itemID) {
  1817.             // Create a name for this window so we can focus it later
  1818.             //
  1819.             // Collection is only used on new notes, so we don't need to
  1820.             // include it in the name
  1821.             name = 'zotero-note-' + itemID;
  1822.             
  1823.             var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1824.                     .getService(Components.interfaces.nsIWindowMediator);
  1825.             var e = wm.getEnumerator('');
  1826.             while (e.hasMoreElements()) {
  1827.                 var w = e.getNext();
  1828.                 if (w.name == name) {
  1829.                     w.focus();
  1830.                     return;
  1831.                 }
  1832.             }
  1833.         }
  1834.         
  1835.         window.open('chrome://zotero/content/note.xul?v=1'
  1836.             + (itemID ? '&id=' + itemID : '') + (col ? '&coll=' + col : '')
  1837.             + (parentItemID ? '&p=' + parentItemID : ''),
  1838.             name, 'chrome,resizable,centerscreen');
  1839.     }
  1840.     
  1841.     
  1842.     function addAttachmentFromDialog(link, id)
  1843.     {
  1844.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  1845.         var fp = Components.classes["@mozilla.org/filepicker;1"]
  1846.                             .createInstance(nsIFilePicker);
  1847.         fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpenMultiple);
  1848.         fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
  1849.         
  1850.         if(fp.show() == nsIFilePicker.returnOK)
  1851.         {
  1852.             var files = fp.files;
  1853.             while (files.hasMoreElements()){
  1854.                 var file = files.getNext();
  1855.                 file.QueryInterface(Components.interfaces.nsILocalFile);
  1856.                 var attachmentID;
  1857.                 if(link)
  1858.                     attachmentID = Zotero.Attachments.linkFromFile(file, id);
  1859.                 else
  1860.                     attachmentID = Zotero.Attachments.importFromFile(file, id);
  1861.             
  1862.                 if(attachmentID && !id)
  1863.                 {
  1864.                     var c = this.getSelectedCollection();
  1865.                     if(c)
  1866.                         c.addItem(attachmentID);
  1867.                 }
  1868.             }
  1869.         }
  1870.     }
  1871.     
  1872.     
  1873.     function addItemFromPage() {
  1874.         if (!Zotero.stateCheck()) {
  1875.             this.displayErrorMessage(true);
  1876.             return false;
  1877.         }
  1878.         
  1879.         var progressWin = new Zotero.ProgressWindow();
  1880.         progressWin.changeHeadline(Zotero.getString('ingester.scraping'));
  1881.         var icon = 'chrome://zotero/skin/treeitem-webpage.png';
  1882.         progressWin.addLines(window.content.document.title, icon)
  1883.         progressWin.show();
  1884.         progressWin.startCloseTimer();
  1885.         
  1886.         var data = {
  1887.             title: window.content.document.title,
  1888.             url: window.content.document.location.href,
  1889.             accessDate: "CURRENT_TIMESTAMP"
  1890.         }
  1891.         
  1892.         var item = this.newItem(Zotero.ItemTypes.getID('webpage'), data);
  1893.         
  1894.         // Automatically save snapshot if pref set
  1895.         if (item.getID() && Zotero.Prefs.get('automaticSnapshots'))
  1896.         {
  1897.             var f = function() {
  1898.                 // We set |noParent|, since child items don't belong to collections
  1899.                 ZoteroPane.addAttachmentFromPage(false, item.getID(), true);
  1900.             }
  1901.             // Give progress window time to appear
  1902.             setTimeout(f, 300);
  1903.         }
  1904.         
  1905.         return item.getID();
  1906.     }
  1907.     
  1908.     
  1909.     /*
  1910.      * Create an attachment from the current page
  1911.      *
  1912.      * |link|      -- create web link instead of snapshot
  1913.      * |itemID|    -- itemID of parent item
  1914.      * |noParent|  -- don't add to current collection
  1915.      */
  1916.     function addAttachmentFromPage(link, itemID, noParent)
  1917.     {
  1918.         if (!Zotero.stateCheck()) {
  1919.             this.displayErrorMessage(true);
  1920.             return;
  1921.         }
  1922.         
  1923.         if (!noParent) {
  1924.             var progressWin = new Zotero.ProgressWindow();
  1925.             progressWin.changeHeadline(Zotero.getString('save.' + (link ? 'link' : 'attachment')));
  1926.             var type = link ? 'web-link' : 'snapshot';
  1927.             var icon = 'chrome://zotero/skin/treeitem-attachment-' + type + '.png';
  1928.             progressWin.addLines(window.content.document.title, icon)
  1929.             progressWin.show();
  1930.             progressWin.startCloseTimer();
  1931.             
  1932.             if (this.itemsView && this.itemsView._itemGroup.isCollection()) {
  1933.                 var parentCollectionID = this.itemsView._itemGroup.ref.getID();
  1934.             }
  1935.         }
  1936.         
  1937.         var f = function() {
  1938.             if (link) {
  1939.                 Zotero.Attachments.linkFromDocument(window.content.document, itemID, parentCollectionID);
  1940.             }
  1941.             else {
  1942.                 Zotero.Attachments.importFromDocument(window.content.document, itemID, false, parentCollectionID);
  1943.             }
  1944.         }
  1945.         // Give progress window time to appear
  1946.         setTimeout(f, 100);
  1947.     }
  1948.     
  1949.     
  1950.     function viewAttachment(itemID, event) {
  1951.         var attachment = Zotero.Items.get(itemID);
  1952.         if (!attachment.isAttachment()) {
  1953.             throw ("Item " + itemID + " is not an attachment in ZoteroPane.viewAttachment()");
  1954.         }
  1955.         
  1956.         if (attachment.getAttachmentLinkMode() == Zotero.Attachments.LINK_MODE_LINKED_URL) {
  1957.             this.loadURI(attachment.getField('url'), event);
  1958.             return;
  1959.         }
  1960.         
  1961.         var file = attachment.getFile();
  1962.         if (file) {
  1963.             var mimeType = attachment.getAttachmentMIMEType();
  1964.             // If no MIME type specified, try to detect again (I guess in case
  1965.             // we've gotten smarter since the file was imported?)
  1966.             if (!mimeType) {
  1967.                 var mimeType = Zotero.MIME.getMIMETypeFromFile(file);
  1968.                 var ext = Zotero.File.getExtension(file);
  1969.                 
  1970.                 // TODO: update DB with new info
  1971.             }
  1972.             var ext = Zotero.File.getExtension(file);
  1973.             var isNative = Zotero.MIME.hasNativeHandler(mimeType, ext);
  1974.             var internal = Zotero.MIME.hasInternalHandler(mimeType, ext);
  1975.             
  1976.             var fileURL = attachment.getLocalFileURL();
  1977.             
  1978.             if (isNative ||
  1979.                     (internal && !Zotero.Prefs.get('launchNonNativeFiles'))) {
  1980.                 this.loadURI(fileURL, event, { attachmentID: itemID});
  1981.             }
  1982.             else {
  1983.                 // Some platforms don't have nsILocalFile.launch, so we just load it and
  1984.                 // let the Firefox external helper app window handle it
  1985.                 try {
  1986.                     file.launch();
  1987.                 }
  1988.                 catch (e) {
  1989.                     window.loadURI(fileURL);
  1990.                 }
  1991.             }
  1992.         }
  1993.         else {
  1994.             this.showAttachmentNotFoundDialog(itemID);
  1995.         }
  1996.     }
  1997.     
  1998.     
  1999.     function viewSelectedAttachment(event)
  2000.     {
  2001.         if (this.itemsView && this.itemsView.selection.count == 1) {
  2002.             this.viewAttachment(this.getSelectedItems(true)[0], event);
  2003.         }
  2004.     }
  2005.     
  2006.     
  2007.     function showSelectedAttachmentInFilesystem()
  2008.     {
  2009.         if (this.itemsView && this.itemsView.selection.count == 1) {
  2010.             var attachment = this.getSelectedItems()[0];
  2011.             
  2012.             if (attachment.getAttachmentLinkMode() != Zotero.Attachments.LINK_MODE_LINKED_URL)
  2013.             {
  2014.                 var file = attachment.getFile();
  2015.                 if (file){
  2016.                     try {
  2017.                         file.reveal();
  2018.                     }
  2019.                     catch (e) {
  2020.                         // On platforms that don't support nsILocalFile.reveal() (e.g. Linux), we
  2021.                         // open a small window with a selected read-only textbox containing the
  2022.                         // file path, so the user can open it, Control-c, Control-w, Alt-Tab, and
  2023.                         // Control-v the path into another app
  2024.                         var io = {alertText: file.path};
  2025.                         window.openDialog('chrome://zotero/content/selectableAlert.xul', "zotero-reveal-window", "chrome", io);
  2026.                     }
  2027.                 }
  2028.                 else {
  2029.                     this.showAttachmentNotFoundDialog(attachment.getID())
  2030.                 }
  2031.             }
  2032.         }
  2033.     }
  2034.     
  2035.     
  2036.     function showAttachmentNotFoundDialog(itemID) {
  2037.         var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
  2038.                 createInstance(Components.interfaces.nsIPromptService);
  2039.         var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
  2040.             + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
  2041.         var index = ps.confirmEx(null,
  2042.             Zotero.getString('pane.item.attachments.fileNotFound.title'),
  2043.             Zotero.getString('pane.item.attachments.fileNotFound.text'),
  2044.             buttonFlags, null, Zotero.getString('general.locate'),
  2045.             null, null, {});
  2046.         
  2047.         if (index == 1) {
  2048.             this.relinkAttachment(itemID);
  2049.         }
  2050.     }
  2051.     
  2052.     
  2053.     function relinkAttachment(itemID) {
  2054.         var item = Zotero.Items.get(itemID);
  2055.         if (!item) {
  2056.             throw('Item ' + itemID + ' not found in ZoteroPane.relinkAttachment()');
  2057.         }
  2058.         
  2059.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  2060.         var fp = Components.classes["@mozilla.org/filepicker;1"]
  2061.                     .createInstance(nsIFilePicker);
  2062.         fp.init(window, Zotero.getString('pane.item.attachments.select'), nsIFilePicker.modeOpen);
  2063.         
  2064.         
  2065.         var file = item.getFile(false, true);
  2066.         var dir = Zotero.File.getClosestDirectory(file);
  2067.         if (dir) {
  2068.             dir.QueryInterface(Components.interfaces.nsILocalFile);
  2069.             fp.displayDirectory = dir;
  2070.         }
  2071.         
  2072.         fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
  2073.         
  2074.         if (fp.show() == nsIFilePicker.returnOK) {
  2075.             var file = fp.file;
  2076.             file.QueryInterface(Components.interfaces.nsILocalFile);
  2077.             item.relinkAttachmentFile(file);
  2078.         }
  2079.     }
  2080.     
  2081.     
  2082.     function reportErrors() {
  2083.         var errors = Zotero.getErrors(true);
  2084.         var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  2085.                    .getService(Components.interfaces.nsIWindowWatcher);
  2086.         var data = {
  2087.             msg: Zotero.getString('errorReport.followingErrors'),
  2088.             e: errors.join('\n\n'),
  2089.             askForSteps: true
  2090.         };
  2091.         var io = { wrappedJSObject: { Zotero: Zotero, data:  data } };
  2092.         var win = ww.openWindow(null, "chrome://zotero/content/errorReport.xul",
  2093.                     "zotero-error-report", "chrome,centerscreen,modal", io);
  2094.     }
  2095.     
  2096.     
  2097.     /*
  2098.      * Display an error message saying that an error has occurred and Firefox
  2099.      * needs to be restarted.
  2100.      *
  2101.      * If |popup| is TRUE, display in popup progress window; otherwise, display
  2102.      * as items pane message
  2103.      */
  2104.     function displayErrorMessage(popup) {
  2105.         var reportErrorsStr = Zotero.getString('errorReport.reportErrors');
  2106.         var reportInstructions =
  2107.             Zotero.getString('errorReport.reportInstructions', reportErrorsStr)
  2108.         
  2109.         // Display as popup progress window
  2110.         if (popup) {
  2111.             var pw = new Zotero.ProgressWindow();
  2112.             pw.changeHeadline(Zotero.getString('general.errorHasOccurred'));
  2113.             var desc = Zotero.getString('general.restartFirefox') + ' '
  2114.                 + reportInstructions;
  2115.             pw.addDescription(desc);
  2116.             pw.show();
  2117.             pw.startCloseTimer(8000);
  2118.         }
  2119.         // Display as items pane message
  2120.         else {
  2121.             var msg = Zotero.getString('general.errorHasOccurred') + ' '
  2122.                 + Zotero.getString('general.restartFirefox') + '\n\n'
  2123.                 + reportInstructions;
  2124.             self.setItemsPaneMessage(msg, true);
  2125.         }
  2126.     }
  2127. }
  2128.  
  2129. window.addEventListener("load", function(e) { ZoteroPane.onLoad(e); }, false);
  2130. window.addEventListener("unload", function(e) { ZoteroPane.onUnload(e); }, false);
  2131.